/***
 * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package br.com.caelum.vraptor.validator;

import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.ResourceBundleDescription;

import br.com.caelum.vraptor.core.SafeResourceBundle;
import br.com.caelum.vraptor.util.FallbackResourceBundle;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;

/**
 * Hamcrest based validation support.
 *
 * Uses:
 * validator.checking(new Validations() {{
 * 		if (that(user, is(notNullValue())) { // that will return if the match was successful
 *	 		that(user.getAge() > 17, "user.age", "user.is.underage"); // boolean assertions
 * 			that(user.getRoles(), hasItem("ADMIN"), "user.roles", "user.is.not.admin"); // hamcrest assertions
 * 		}
 * }});
 *
 * You can use any hamcrest Matcher. Some helpful matchers can be found on org.hamcrest.Matchers.
 *
 * @author Guilherme Silveira
 * @author Lucas Cavalcanti
 */
public class Validations {

	private final List<Message> errors = new ArrayList<Message>();
	private Supplier<ResourceBundle> bundle;

	public Validations(ResourceBundle bundle) {
		this.bundle = Suppliers.ofInstance(bundle);
	}

	public Validations() {
		this(new SafeResourceBundle(ResourceBundle.getBundle("messages"), true));
	}

	public <T> boolean that(T id, Matcher<? super T> matcher) {
	return that(id, matcher, "", null);
	}

	public <T> boolean that(T id, Matcher<? super T> matcher, String category) {
	return that(id, matcher, category, null);
	}

	public <T> boolean that(T id, Matcher<? super T> matcher, I18nParam category) {
		return that(id, matcher, category, null);
	}

	public <T> boolean that(T actual, Matcher<? super T> matcher, String category, String reason, Object... messageParameters) {
	return genericThat(actual, matcher, category, reason, messageParameters);
	}

	public <T> boolean that(T actual, Matcher<? super T> matcher, I18nParam category, String reason, Object... messageParameters) {
		return genericThat(actual, matcher, category, reason, messageParameters);
	}

	public boolean that(boolean assertion, String category, String reason, Object... messageParameters) {
	return genericThat(assertion, category, reason, messageParameters);
	}

	public boolean that(boolean assertion, I18nParam category, String reason, Object... messageParameters) {
		return genericThat(assertion, category, reason, messageParameters);
	}

	protected I18nParam i18n(String key) {
		return new I18nParam(key);
	}

	/**
	 * Returns the list of errors.
	 */
	public List<Message> getErrors() {
		for (Message message : errors) {
			if (message instanceof I18nMessage) {
				((I18nMessage) message).setLazyBundle(bundle);
			}
		}
	return errors;
	}

	/**
	 * Returns the list of errors, using given resource bundle.
	 */
	public List<Message> getErrors(ResourceBundle bundle) {
		return getErrors(Suppliers.ofInstance(bundle));
	}
	/**
	 * Returns the list of errors, using given resource bundle.
	 */
	public List<Message> getErrors(final Supplier<ResourceBundle> bundle) {
		final Supplier<ResourceBundle> oldBundle = this.bundle;
		this.bundle = new Supplier<ResourceBundle>() {
			public ResourceBundle get() {
				if (isDefaultBundle(oldBundle)) {
					return new SafeResourceBundle(bundle.get());
				} else {
					return new FallbackResourceBundle(oldBundle.get(), bundle.get());
				}
			}
		};
		return getErrors();
	}

	private boolean isDefaultBundle(Supplier<ResourceBundle> bundle) {
		return bundle.get() instanceof SafeResourceBundle && ((SafeResourceBundle) bundle.get()).isDefault();
	}

	/**
	 * Adds a list of errors to the error list.
	 * @return
	 */
	public Validations and(List<Message> errors) {
	this.errors.addAll(errors);
	return this;
	}

	/**
	 * Adds a single error message to the error list.
	 */
	public Validations and(Message error) {
	this.errors.add(error);
	return this;
	}

	private <T> boolean genericThat(T actual, Matcher<? super T> matcher, Object category, String reason, Object... messageParameters) {
		if (!matcher.matches(actual)) {
		if (reason != null) {
			errors.add(i18nMessage(category, reason, messageParameters));
		} else {
		Description description = new ResourceBundleDescription();
		description.appendDescriptionOf(matcher);
		errors.add(i18nMessage(category, description.toString(), actual));
		}
		return false;
	}
	return true;
	}

	private I18nMessage i18nMessage(Object category, String reason, Object... messageParameters) {
		if (category instanceof I18nParam) {
			return new I18nMessage((I18nParam) category, reason, messageParameters);
		}
		return new I18nMessage(category.toString(), reason, messageParameters);
	}

	private boolean genericThat(boolean assertion, Object category, String reason, Object... messageParameters) {
		if (!assertion) {
		errors.add(i18nMessage(category, reason, messageParameters));
	}
	return assertion;
	}
	
}
